Most programmers never need to deal with files directly, instead they can use readers and writers (of module Stores -> 5.4) which are set up already by Oberon!
Module Files provides the abstractions necessary to handle most aspects of a hierarchical file system. A file is a sequence of bytes. Several access paths can be open simultaneously on the same file, possibly at different positions.
A file and its access paths are modeled as separate data structures, namely as File and Reader/Writer. Where a statement applies both to readers and writers, the term "rider" will be used. An open rider is never closed explicitly, and an application can create as many riders as it needs.
Picture 5.2a File with three Riders
Each file resides at some location in the file hierarchy (i.e. in a subdirectory). In Oberon, a location is described by a locator object. A directory object provides a procedure which creates a locator, given a path name in the host platform's file name syntax. Most other directory operations take a locator as parameter, to find the specific subdirectory where the operation should be performed.
For temporary files, a system-specific (implicit) location is used. Temporary files are used as scratch files, and cannot be registered in the file directory. Module Dialog provides further sources for file locators, via standard file dialogs.
A file itself is specified by a location and a name. The name is the file's local name at the given location, i.e. it cannot be a path name.
A directory object provides three procedures to access a file: New, Temp, and Old. New creates a new file. This file already has a particular location, but is anonymous, i.e. it has no name (yet). When the file's contents are written, the file can be registered under a given name, possibly replacing an already existing file which in turn becomes anonymous itself. File registration is an atomic action, which reduces the danger that a file is replaced by a new, but incomplete or corrupted, file.
Anonymous files for which no more riders exist are automatically deleted by the garbage collector, at an appropriate time.
Temp creates a temporary file. Such a file is never registered, and thus remains anonymous.
Old looks up and opens an existing file, given its name and location. The file may either be opened in shared or in exclusive mode. "shared" means that it may be looked up and opened by several programs simultaneously, but that none may alter it (immutable file). Even if a file has been opened, its entry in the file directory is replaced when a new file is registered at the same location and under the same name. In this case, the old file remains accessible through the existing file readers. However, looking up this file with procedure Old yields the most recently registered file version. When no more riders on an older file version exist, the disk space occupied by the file is reclaimed by the garbage collector eventually.
Opening a file in shared mode is the rule in Oberon; opening a file in exclusive mode is an infrequent exception. "exclusive" means that at most one program may open a file. As long as the file is not closed again, other programs remain locked out, i.e. Old on the same file fails. An exclusively opened file may be modified (mutable file), which is useful for simple data base applications. Registering a new file under the same name as an exclusively opened file has the same effect as for shared files, i.e. the existing file becomes anonymous, and is garbage collected eventually.
A file can be opened in exclusive mode, closed, and then be opened again in shared mode, for example. However, it can never be open in exclusive and in shared mode simultaneously.
Open files for which no more riders exist are automatically closed by the garbage collector at an appropriate time. For files opened in exclusive mode, it is recommended that they be closed explicitly, in order to make them accessible again to other programs as early as possible.
A directory object represents all accessible files (not just one subdirectory), independent of their location in the file hierarchy. There is exactly one file hierarchy. However, every Oberon service may implement its own file directory object. Such an object represents exactly the same file hierarchy, but may provide different ways to look up files, e.g. by applying default search paths, or it may define a current directory relative to which path names are evaluated, etc.
CONST exclusive, shared
Values which can be passed to the Directory.Old.shared parameter, to determine whether a file should be opened in shared or in exclusive mode.
TYPE Name
String type for file names.
TYPE Type
String type for file type names.
TYPE Locator
Interface
A file locator identifies a location in the file system.
File locators are used internally, and sometimes in commands which operate on non-Oberon files.
File locators are extended internally.
res: LONGINT
Directory operations return their results in the locator's res field.
The following result codes are predefined:
res = 0 no error
res = 1 invalid parameter (name or locator)
res = 2 location or file not found
res = 3 file already exists
res = 4 write-protection
res = 5 io error
res = 6 access denied
res = 7 illegal file type
res = 8 cancelled
A particular Oberon implementation may return additional, platform-specific, error codes. These error codes always have negative values.
PROCEDURE (l: Locator) This (path: ARRAY OF CHAR): Locator
Interface
This evaluates a relative path, starting from the location specified by l.
path # NIL 20
result # NIL
l.res = 0 no error
result = NIL
l.res = 1 invalid name
l.res = 2 location or file not found
l.res = 4 write-protection
l.res = 5 io error
TYPE File
Interface
A file is a carrier for a linear sequence of bytes, which typically resides on a hard disk or similar device.
Files are allocated by file directories.
Files are used by commands which operate on non-Oberon files.
Returns a reader which has the appropriate type (for this file type). If old = NIL, then a new reader is allocated. If old # NIL and old has the appropriate type, old is returned. Otherwise, a new reader is allocated. The returned reader is connected to f, its eof field is set to FALSE, and its position is somewhere on the file. If an old reader is passed as parameter, the old position will be retained if possible.
If an old reader is passed as parameter, it is the application's responsibility to guarantee that it is not in use anymore. Passing an unused old reader is recommended because it avoids unnecessary allocations.
Returns a writer which has the appropriate type (for this file type). If old = NIL, then a new writer is allocated. If old # NIL and old has the appropriate type, old is returned. Otherwise, a new writer is allocated. The returned writer is connected to f, and its position is somewhere on the file. If an old writer is passed as parameter, the old position will be retained if possible.
If an old writer is passed as parameter, it is the application's responsibility to guarantee that it is not in use anymore. Passing an unused old writer is recommended because it avoids unnecessary allocations.
Read-only files allow no writers at all. In such cases, NewWriter returns NIL.
result # NIL
old # NIL & old.Base() = f
result.Pos() = old.Pos()
old = NIL OR old.Base() # f
result.Pos() = f.Length()
result = NIL
read-only file
PROCEDURE (f: File) Flush
Empty
To guarantee consistency of the file, Flush should be called after the last writer operation. Superfluous calls of Flush have no effect.
Register makes an anonymous file permanently available. If a file with the same name at the same location already exists, it is deleted first. Register can be considered as an atomic action.
Only files opened with procedure New may be registered. Trying to register a file opened with Old results in a precondition violation error.
If an already existing file is deleted during Register, only its entry in the file directory is removed. The file's contents are still available to existing file riders. The space occupied by a file is reclaimed at an unspecified time after no more riders on it exist anymore.
The file f and the riders operating on file f are not valid anymore after registering f, i.e. no more file or rider operations may be performed on it. However, the registered file can be retrieved by procedure Old again.
Register may call Flush internally, and closes the file.
Each registered file has a file type, which is passed to Register in the type parameter. The empty string can be passed, resulting in the standard document file type of Oberon.
f is anonymous and not temporary 20
name # "" 21
name is not a file name 22
res = 0 no error
res = 2 location not found
res = 4 write-protection
res = 5 io error
res = 7 illegal file type
res = 8 cancelled
PROCEDURE (f: File) Close
Interface
Closes an open file. Close does nothing if the file is not open or if it has been opened in "shared" mode. If a call to New or Old is not balanced by a call to Close, the Close is later performed automatically, at an unspecified time. If it is known that a file won't be used again, it is recommended to call its Close procedure.
The file f and the riders operating on file f are not valid anymore after closing f, i.e. no more file or rider operations may be performed on it. However, the closed file can be retrieved and opened again by procedure Old.
Close may call Flush internally.
Close should (but need not necessarily) be called explicitly after a file is not needed anymore.
PROCEDURE (f: File) InitType (type: Type)
Initializes the file's type field.
type # "" 20
f.type = "" 21
TYPE Reader
Interface
Reading access path to a file carrier.
Readers are allocated by their base files.
Readers are used by commands which read non-Oberon files and operate at the byte level.
Readers are extended internally.
eof: BOOLEAN
Set when it has been attempted to read the byte after the end of the file (by ReadByte or ReadBytes). Reset when the reader is generated or positioned.
PROCEDURE (r: Reader) Base (): File
Interface
Returns the file to which the reader is currently connected.
result # NIL
PROCEDURE (r: Reader) Pos (): LONGINT
Interface
Returns the reader's current position.
0 <= result <= r.Base().Length()
PROCEDURE (r: Reader) SetPos (pos: LONGINT)
Interface
Sets the reader's current position to pos and clears the eof flag.
pos >= 0 20
pos <= r.Base().Length() 21
r.Pos() = pos
~r.eof
PROCEDURE (r: Reader) ReadByte (VAR x: CHAR)
Interface
Attempts to read the byte after the current position. If successful, it increments the position by one. If the current position (before reading) is at the end of the available data, i.e. Pos equals the carrier data's length, then r.eof is set.
Attempts to read len bytes after the current position. It increments the position by the number of bytes which have been read successfully. If reading is continued beyond the file's length, then r.eof is set. The data are transferred to the array x starting at element beg.
ReadBytes is implemented in terms of ReadByte operations, but may be overwritten for efficiency.
ReadBytes internally may call SetPos.
beg >= 0 20
len >= 0 21
beg + len <= LEN(x) 22
r.Pos()' <= r.Length() - len
r.Pos() = r.Pos()' + len
~r.eof
len bytes read after r.Pos()' and transferred into x
r.Pos()' > r.Length() - len
r.Pos() = r.Length()
r.eof
r.Length() - r.Pos()' bytes read after r.Pos()' and transferred into x
TYPE Writer
Writing access path to a file carrier.
Writers are allocated by their base files.
Writers are used by commands which write non-Oberon files and operate at the byte level.
Writers are extended internally.
PROCEDURE (w: Writer) Base (): File
Interface
Returns the file to which the writer is currently connected.
result # NIL
PROCEDURE (w: Writer) Pos (): LONGINT
Interface
Returns the writer's current position.
0 <= result <= w.Base().Length()
PROCEDURE (w: Writer) SetPos (pos: LONGINT)
Interface
Sets the writer's current position to pos.
pos >= 0 20
pos <= w.Base().Length() 21
w.Pos() = pos
PROCEDURE (w: Writer) WriteByte (x: CHAR)
Interface
Writes a byte after the current position, then increments the current position. If the current position is at the end of the carrier data, the writer's length is incremented also.
Writes len bytes after the current position and increments the position accordingly. If necessary the stream's length is increased. The data are transferred from array x starting with element beg.
WriteBytes is implemented in terms of WriteByte operations, but may be overwritten for efficiency.
WriteBytes internally may call SetPos.
beg >= 0 20
len >= 0 21
beg + len <= LEN(x) 22
len bytes transferred from variable x to carrier
w.Pos() = w.Pos()' + len
w.Pos()' + len <= w.Base().Length()'
w.Base().Length() = w.Base().Length()'
w.Pos()' + len > w.Base().Length()'
w.Base().Length() = w.Pos()' + len
TYPE Directory
Interface
Directory for the lookup in and manipulation of file directories.
File directories are allocated by Oberon.
File directories are used by commands which operate on non-Oberon files.
File directories are extended internally.
PROCEDURE (d: Directory) This (path: ARRAY OF CHAR): Locator
Interface
Returns a locator, given a path name in the host platform's syntax.
This may perform some validity checks, e.g. whether the syntax of the name is correct or whether the name denotes an existing subdirectory. Passing the empty string yields a default location.
result # NIL
result.res = 0
legal locator
result.res # 0
illegal locator
PROCEDURE (d: Directory) Temp (): File
Interface
Returns a temporary file. This file is anonymous, i.e. not registered in a directory. (In host file systems where anonymous files are not directly supported, they may appear under temporary names in a suitable subdirectory.) Registration is not possible on a temporary file.
A temporary file always has both read and write capabilities (mutable file).
result # NIL
PROCEDURE (d: Directory) New (loc: Locator): File
Interface
Returns a new file object (or NIL if this is not possible). This file is anonymous, i.e. not yet registered in the directory. (In host file systems where anonymous files are not directly supported, they may appear under temporary names in subdirectory loc.) If the file is registered later, it will appear in the subdirectory specified by loc.
A new file always has both read and write capabilities (mutable file).
Looks up and opens a file with name name at location loc. It returns this file (or NIL if this is not possible). Parameter shared determines whether the returned file is in shared or in exclusive mode. A shared file provides read-only access. This means that several applications may read the file simultaneously, but it may not be modified. An exclusively opened file provides exclusive read and write access. This means that both read and write access are denied to any other application. Note however, that the application may pass on the file pointer to wherever it likes. The point is, another application cannot gain access to the file solely via the file directory, without cooperation of the application which currently has access. Moreover, "exclusive" access does not imply that only one rider may be active on the file.
A file is usually opened in shared mode. To change its contents, a new file is generated and then registered under the old name. If only a small part of the data is actually changed, it may be more appropriate to use the exclusive mode instead, e.g. when implementing simple data bases. In this case, the file should be closed explicitly as soon as it isn't needed anymore.